﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace CoreLib.Communication.Packets
{
	/// <summary>
	/// Layer class for pointers with write/read functions
	/// </summary>
    public unsafe class Packet : IDisposable
    {
    	/// <summary>
    	/// Gets the maximum allowed size for a packet
    	/// </summary>
        public const int MaxSize = 1024;
        /// <summary>
        /// Gets the IntPtr for the packet
        /// </summary>
        public IntPtr SafePointer { get; private set; }
        /// <summary>
        /// Gets the unsafe pointer for the packet
        /// </summary>
        public sbyte* UnsafePointer { get; private set; }
        /// <summary>
        /// Gets the size(memory) used by this packet
        /// </summary>
        public int Size { get; private set; }
        /// <summary>
        /// Gets the current position of the pointer
        /// </summary>
        public int Position { get; private set; }
		
        private bool disposed{get; set;}
        
        /// <summary>
        /// Creates a new instance of the packet class
        /// </summary>
        /// <param name="size">Size of the packet that will be allocated</param>
        internal Packet(int size)
        {
            this.SafePointer = Marshal.AllocHGlobal(size);
            this.UnsafePointer = (sbyte*)SafePointer;
            this.Size = size;
            this.Position = 0;
            ZeroFill(size);
            Seek(0, Origin.Begin);
        }

        public static Packet CreatePacket(int requiredSize)
        {
            return PacketRecycler.Pop(requiredSize);
        }

        /// <summary>
        /// Reset all the instance values to make it reusable
        /// </summary>
        /// <param name="size">new size for the packet</param>
        public void Reset(int size)
        {
            Seek(0, Origin.Begin);
            this.Size = size;
            ZeroFill(size);
            Seek(0, Origin.Begin);
        }

        /// <summary>
        /// Fill with zeros the packet
        /// </summary>
        /// <param name="size">length that will be zero filled</param>
        private void ZeroFill(int size)
        {
        	for(int pos = Position; pos < size % 4; pos++)
        	{
        		Write((int)0);
        	}
            for (int pos = Position; pos < size; pos++)
        	{
        		Write((sbyte)0);
        	}
        }
        
        /// <summary>
        /// returns true if the packet has enough space for a write/read operation, or throw an
        /// exception if there is not enough space
        /// </summary>
        /// <param name="requiredSize">required size for the write/read operation</param>
        bool EnsureCapacity(int requiredSize)
        {
            if (Position + requiredSize <= Size)
                return true;

           throw new Exception("Not enough memory for read/write operation");
        }
		
        /// <summary>
        /// Writes an string and increments the position by string length
        /// </summary>
        /// <param name="val">string that will be written</param>
        public void Write(string val)
        {
            WriteByteArray(Encoding.ASCII.GetBytes(val), 0, val.Length);
        }
		
        /// <summary>
        /// Writes an string and increments the position by the count param
        /// </summary>
        /// <param name="val">string that will be written</param>
        /// <param name="count">amount that will be increased the position</param>
        public void WriteCString(string val, int count)
        {
            Write(val);
            Position += count - val.Length;
        }
		
        /// <summary>
        /// Writes a byte array and increases the position by the array lengts
        /// </summary>
        /// <param name="val">byte array that will be written</param>
        public void WriteByteArray(byte[] val)
        {
            EnsureCapacity(val.Length);
            Marshal.Copy(val, 0, (IntPtr)(UnsafePointer + Position), val.Length);
            Position += val.Length;
        }
		
        /// <summary>
        /// Writes a segment of a byte array and increases the position by the amount param
        /// </summary>
        /// <param name="val">byte array containing the segment that will be written</param>
        /// <param name="amount">amount that will be increased the position</param>
        public void WriteByteArray(byte[] val, int amount)
        {
            EnsureCapacity(amount);
            Marshal.Copy(val, 0, (IntPtr)(UnsafePointer + Position), amount);
            Position += amount;
        }
		
        /// <summary>
        /// Writes a segment of a byte array and increases the position by the amount param
        /// </summary>
        /// <param name="val">byte array containing the segment that will be writteb</param>
        /// <param name="index">start position of the segment in the byte array</param>
        /// <param name="amount">amount that will be increased the position</param>
        public void WriteByteArray(byte[] val, int index, int amount)
        {
            EnsureCapacity(amount);
            Marshal.Copy(val, index, (IntPtr)(UnsafePointer + Position), amount);
            Position += amount;
        }
		
        /// <summary>
        /// Writes a sbyte on the packet and increases the position by 1
        /// </summary>
        /// <param name="val">sbyte that will be written</param>
        public void Write(sbyte val)
        {
            EnsureCapacity(1);
            *(sbyte*)(UnsafePointer + Position) = val;
            Position += 1;
        }

        /// <summary>
        /// Writes a short on the packet and increases the position by 2
        /// </summary>
        /// <param name="val">short that will be written</param>
        public void Write(short val)
        {
            EnsureCapacity(2);
            *(short*)(UnsafePointer + Position) = val;
            Position += 2;
        }

        /// <summary>
        /// Writes a int on the packet and increases the position by 4
        /// </summary>
        /// <param name="val">int that will be written</param>
        public void Write(int val)
        {
            EnsureCapacity(4);
            *(int*)(UnsafePointer + Position) = val;
            Position += 4;
        }

        /// <summary>
        /// Writes a long on the packet and increases the position by 8
        /// </summary>
        /// <param name="val">long that will be written</param>
        public void Write(long val)
        {
            EnsureCapacity(8);
            *(long*)(UnsafePointer + Position) = val;
            Position += 8;
        }

        /// <summary>
        /// Writes a byte on the packet and increases the position by 1
        /// </summary>
        /// <param name="val">byte that will be written</param>
        public void Write(byte val)
        {
            Write((sbyte)val);
        }

        /// <summary>
        /// Writes a ushort on the packet and increases the position by 2
        /// </summary>
        /// <param name="val">ushort that will be written</param>
        public void Write(ushort val)
        {
            Write((short)val);
        }

        /// <summary>
        /// Writes a uint on the packet and increases the position by 4
        /// </summary>
        /// <param name="val">uint that will be written</param>
        public void Write(uint val)
        {
            Write((int)val);
        }

        /// <summary>
        /// Writes a ulong on the packet and increases the position by 8
        /// </summary>
        /// <param name="val">ulong that will be written</param>
        public void Write(ulong val)
        {
            Write((long)val);
        }

      	/// <summary>
      	/// Reads a string from the packet
      	/// </summary>
      	/// <param name="length">Length of the string</param>
        public string ReadString(int length)
        {
        	var str = new String(UnsafePointer, Position, length, Encoding.ASCII);
        	Seek(length, Origin.Current);
        	return str.TrimEnd('\0');
        }

        /// <summary>
        /// Reads a byte array from the packet
        /// </summary>
        /// <param name="amount">length of the byte array</param>
        public byte[] ReadByteArray(int amount)
        {
            EnsureCapacity(amount);
            byte[] buffer = new byte[amount];
            Marshal.Copy((IntPtr)(UnsafePointer + Position), buffer, 0, amount);
            Position += amount;
            return buffer;
        }
        
		/// <summary>
		/// Reads a sbyte from the packet
		/// </summary>
        public sbyte ReadSByte()
        {
            EnsureCapacity(1);
            var value = *(sbyte*)(UnsafePointer + Position);
            Position += 1;
            return value;
        }

        /// <summary>
		/// Reads a short from the packet
		/// </summary>
        public short ReadInt16()
        {
            EnsureCapacity(2);
            var value = *(short*)(UnsafePointer + Position);
            Position += 2;
            return value;
        }

        /// <summary>
		/// Reads an int from the packet
		/// </summary>
        public int ReadInt32()
        {
            EnsureCapacity(4);
            var value = *(int*)(UnsafePointer + Position);
            Position += 4;
            return value;
        }

        /// <summary>
		/// Reads a long from the packet
		/// </summary>
        public long ReadInt64()
        {
            EnsureCapacity(8);
            var value = *(long*)(UnsafePointer + Position);
            Position += 8;
            return value;
        }
        
		/// <summary>
		/// Reads a byte from the packet
		/// </summary>
        public byte ReadByte()
        {
            return (byte)ReadSByte();
        }
        
		/// <summary>
		/// Reads an ushort from the packet
		/// </summary>
        public ushort ReadUInt16()
        {
            return (ushort)ReadInt16();
        }

        /// <summary>
		/// Reads an uint from the packet
		/// </summary>
        public uint ReadUInt32()
        {
            return (uint)ReadInt32();
        }

        /// <summary>
		/// Reads an ulong from the packet
		/// </summary>
        public ulong ReadUInt64()
        {
            return (ulong)ReadInt64();
        }

        /// <summary>
        /// Sets a new position for the pointer
        /// </summary>
        /// <param name="position">New position</param>
        /// <param name="origin">Origin of the position</param>
        public void Seek(int position, Origin origin)
        {
            switch (origin)
            {
                case Origin.Begin:
                    Position = position;
                    break;
                case Origin.Current:
                    Position += position;
                    break;
                case Origin.End:
                    Position = Size - position;
                    break;
            }
        }

        private void Dispose(bool disposing)
        {
        	if(disposing)
        	{
        		//free managed resources
        	}
        	Marshal.FreeHGlobal(SafePointer);
            UnsafePointer = null;
        }
        
        /// <summary>
        /// Free al the resources used by the Packet
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
		
        /// <summary>
        /// Gets a byte array representation of the packet
        /// </summary>
        public byte[] ToArray()
        {
            var buffer = new byte[Size];
            Marshal.Copy(SafePointer, buffer, 0, Size);
            return buffer;
        }
    }
}
